package com.lujianfei.imagesearch.utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.lujianfei.imagesearch.Constants;

public class Convolution {

	Matrix kernel;
	Matrix image;

	public void fillInKernel(double[][] data) {
		for (int i = 0; i < kernel.height; i++) {
			for (int j = 0; j < kernel.width; j++) {
				kernel.datas[i][j] = data[i][j];
			}
		}
	}

	public void fillInImage(double[][] data) {
		if (null == image) {
			return;
		}
		int width = image.width + image.padding * 2;
		int height = image.height + image.padding * 2;
		int padding = image.padding;
		try {
			for (int i = 0; i < width; i++) {
				for (int j = 0; j < image.padding; j++) {
					image.datas[j][i] = image.fillvalue;
					image.datas[height - 1 - j][i] = image.fillvalue;
				}
			}
		} catch (Exception e) {
			System.err.println("first:" + e);
		}
		try {
			for (int i = 0; i < height; i++) {
				for (int j = 0; j < image.padding; j++) {
					image.datas[i][j] = image.fillvalue;
					image.datas[i][width - 1 - j] = image.fillvalue;
				}
			}
		} catch (Exception e) {
			System.err.println("second:" + e);
		}
		try {
			for (int i = padding; i < padding + image.width; i++) {
				for (int j = padding; j < padding + image.height; j++) {
					image.datas[j][i] = data[j - padding][i - padding];
				}
			}
		} catch (Exception e) {
			System.err.println("third:" + e);
		}
	}
	
	/**
	 * 滤波算法
	 * @see https://blog.csdn.net/fastbox/article/details/7984721
	 * @param type
	 */
	public void operation(int type) {
		try {
			Matrix cloneImage = (Matrix) image.clone();
			int width = cloneImage.width;
			int height = cloneImage.height;
			for (int i = 0; i < height; i++) {
				for (int j = 0; j < width; j++) {
					switch(type) {
						case Constants.REMOVE_NOISE_MODE.MEAN_FILTER:
							mutliAndSum(cloneImage,image, i, j, kernel);
							break;
						case Constants.REMOVE_NOISE_MODE.MEDIUM_FILTER:
							pickMedium(cloneImage,image, i, j, kernel);
							break;
						case Constants.REMOVE_NOISE_MODE.MAXMIN_FILTER:
							pickMaxmin(cloneImage,image, i, j, kernel);
							break;
					}
				}
			}
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void mutliAndSum(Matrix src,Matrix dst, int ii, int jj, Matrix kernel2) {
		// TODO Auto-generated method stub
		double sum = 0;
		for (int i = 0; i < kernel.height; i++) {
			for (int j = 0; j < kernel.width; j++) {
				sum += src.datas[ii + i][jj + j] * kernel2.datas[i][j];
			}
		}
		dst.datas[ii + src.padding][jj + src.padding] = sum;
	}
	
	private void pickMedium(Matrix src,Matrix dst, int ii, int jj, Matrix kernel2) {
		// TODO Auto-generated method stub
		List<Double> sortData = new ArrayList<>();
		for (int i = 0; i < kernel.height; i++) {
			for (int j = 0; j < kernel.width; j++) {
				double key = src.datas[ii + i][jj + j];
				if(!sortData.contains(key)) {
					sortData.add(key);
				}
			}
		}
		Collections.sort(sortData);
		// 这里需要加以改进，不然中值滤波会出现去不了盐噪点的问题
		Double result = sortData.get(sortData.size()/2);
		if (255f == result && sortData.size() > 1) {
			result = sortData.get(sortData.size()/2 - 1);
		}
		dst.datas[ii + src.padding][jj + src.padding] = result;
	}
	
	private void pickMaxmin(Matrix src,Matrix dst, int ii, int jj, Matrix kernel2) {
		// TODO Auto-generated method stub
		double max = Integer.MIN_VALUE ,min = Integer.MAX_VALUE;
		double middle = src.datas[ii + kernel.height/2][jj + kernel.height/2];
		for (int i = 0; i < kernel.height; i++) {
			for (int j = 0; j < kernel.width; j++) {
				double key = src.datas[ii + i][jj + j];
				if(ii + i == (ii + kernel.height/2) && jj + j == (jj + kernel.height/2)) { 
					continue;
				}
				
				if (key > max) {
					max = key;
				}
				if (key < min) {
					min = key;
				}
			}
		}
		double result = middle;
		if(middle > max) {
			result = max;
		} else if(middle < min) {
			result = min;
		}
		dst.datas[ii + src.padding][jj + src.padding] = result;
	}
	
	public void fillOutImage(double[][] data) {
		for (int i = 0; i < image.height; i++) {
			for (int j = 0; j < image.width; j++) {
				data[i][j] = image.datas[image.padding + i][image.padding + j];
			}
		}
	}
	
	public void printKernel() {
		System.out.println("====================Kernel====================");
		for (int i = 0; i < kernel.height; i++) {
			for (int j = 0; j < kernel.width; j++) {
				System.out.print(String.format("%s\t", kernel.datas[i][j]));
			}
			System.out.println();
		}
	}

	public void printImage() {
		System.out.println("====================Image====================");
		int width = image.width + image.padding * 2;
		int height = image.height + image.padding * 2;
		for (int i = 0; i < height; i++) {
			for (int j = 0; j < width; j++) {
				System.out.print(String.format("%s\t", image.datas[i][j]));
			}
			System.out.println();
		}
	}
	
	public void print(double[][] d,int height,int width) {
		System.out.println("====================Print====================");
		for (int i = 0; i < height; i++) {
			for (int j = 0; j < width; j++) {
				System.out.print(String.format("%s\t", d[i][j]));
			}
			System.out.println();
		}
	}
	
	public void printValidImage() {
		System.out.println("====================Valid Image====================");
		for (int i = 0; i < image.height; i++) {
			for (int j = 0; j < image.width; j++) {
				System.out.print(String.format("%s\t", image.datas[image.padding + i][image.padding + j]));
			}
			System.out.println();
		}
	}

	public static class Matrix implements Cloneable {
		int width;
		int height;
		double[][] datas;// 数据元素
		int padding;
		int fillvalue = 0;

		public Matrix(int width, int height, int padding, int fillvalue) {
			super();
			this.width = width;
			this.height = height;
			this.padding = padding;
			this.datas = new double[height + padding * 2][width + padding * 2];
			this.fillvalue = fillvalue;
		}
		
		@Override
	    protected Object clone() throws CloneNotSupportedException {
	        Object obj=super.clone();
	        Matrix m = (Matrix) obj;
	        Matrix newM = new Matrix(m.width,m.height,m.padding,m.fillvalue);
	        int newwidth = newM.width + newM.padding * 2;
			int newheight = newM.height + newM.padding * 2;
			for (int i = 0; i < newheight; i++) {
				for (int j = 0; j < newwidth; j++) {
					newM.datas[i][j] = m.datas[i][j];
				}
			}
	        return newM;
		}
		
		@Override
		public boolean equals(Object obj) {
			// TODO Auto-generated method stub
			Matrix a=((Matrix)obj);
			int newwidth = width + padding * 2;
			int newheight = height + padding * 2;
			for (int i = 0; i < newheight; i++) {
				for (int j = 0; j < newwidth; j++) {
					if(datas[i][j] != a.datas[i][j]) {
						return false;
					}
				}
			}
			return true;
		}
		
		@Override
		public int hashCode() {
			// TODO Auto-generated method stub
			return super.hashCode();
		}
	}

	public static class Builder {
		Matrix kernel;
		Matrix image;

		public Builder() {

		}

		public Builder setKernel(int height, int width) {
			kernel = new Matrix(width, height, 0, 0);
			return this;
		}

		public Builder setImage(int height, int width, int padding, int fillvalue) {
			image = new Matrix(width, height, padding, fillvalue);
			return this;
		}

		public Convolution create() {
			Convolution convolutionHelper = new Convolution();
			convolutionHelper.kernel = kernel;
			convolutionHelper.image = image;
			return convolutionHelper;
		}
	}

}
